package com.juli.school.service.utils;

import java.io.InputStream;
import java.io.PrintWriter;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.logging.Logger;

import javax.sql.DataSource;

/**
 * @author wjp
 * @date 2017年8月2日
 */
public class JdbcPool implements DataSource {

	// 模拟一个“连接池容器”, 存放connection对象（一定要是弄成线程安全的，或者同步add,remove会有问题的）
	private static List<Connection> list = Collections.synchronizedList(new LinkedList<Connection>());

	private static JdbcPool jdbcPool = new JdbcPool();

	private static Properties config = new Properties();

	/**
	 * 静态代码块里存放只需要执行一次的： 1.读取配置文件 2.初始化连接池里面的connection对象
	 */
	static {
		try {
			/**
			 * 读取配置文件到config中 加载mysql驱动程序
			 */
			InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("config/datasource.properties");
			config.load(in);
			Class.forName("com.mysql.jdbc.Driver");
			/**
			 * 
			 * 初始化连接池容器，让它初始化具备10个的connection对象
			 */
			for (int i = 0; i < 10; i++) {
				Connection conn = DriverManager.getConnection(config.getProperty("school.jdbc.url"), config.getProperty("school.jdbc.username"),
						config.getProperty("school.jdbc.password"));
				list.add(conn);
			}
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	private JdbcPool() {
	}

	public static JdbcPool getInstance() {
		return jdbcPool;
	}

	/**
	 * 分配，释放connection的逻辑处理： connection的获取：每次都获取list的头一个节点对象
	 * connection的释放：往list中加入一个connection对象
	 * 
	 * 因此当conn.close()被调用的时候，我们需要往jdbcPool的list中加入connection对象,
	 * 这样就需要“增强”Connection类的close()方法
	 * 
	 * 
	 */

	public Connection getConnection() throws SQLException {

		if (list.size() <= 0) {
			throw new RuntimeException("数据库连接池已经用完，请等待！");
		}
		// 数据库连接池中还有connection对象的话
		Connection conn = list.get(0);
		list.remove(0);
		StrongConnection strongConnection = new StrongConnection(conn);
		System.out.println("有个connection被申请了,从list中移除!当前连接池中connection数量：" + list.size());
		return strongConnection;
	}

	/*
	 * 增强一个类的步骤如下： 1.定义一个类，实现与被增强相同的接口 2.在类中定义一个变量，记住被增强对象(这样才可以调用这个对象的方法)
	 * 3.定义一个构造函数，接收被增强对象 4.覆盖想增强的方法 5.对于不想增强的方法，直接调用目标对象（被增强对象）的方法
	 * 
	 * 如下： 要被增强的对象：Connection
	 * 那么要实现Connection的所有接口，定义一个变量Connection对象记住这个Connection类对象
	 * 用一个构造函数来接收这个对象，覆盖close()方法（我们想增强的方法）,其他的不想增强的方法 用 this.conn.方法名 去调用就OK了
	 * 
	 */
	class StrongConnection implements Connection {
		private Connection conn;

		public StrongConnection(Connection conn) {
			this.conn = conn;
		}

		public void close() throws SQLException {
			JdbcPool.list.add(this.conn);
			System.out.println("有个connection释放了,回到连接池容器list中!当前连接池中connection数量：" + list.size());
		}

		/**
		 * 下面的方法实现 全部按照 this.conn.方法名 的方式调用，由于太多了哈，所以我就写前面几个
		 * （等之后博主写动态代理技术实现增强类的时候就不需要这么麻烦了！）
		 */

		public <T> T unwrap(Class<T> iface) throws SQLException {
			return this.conn.unwrap(iface);
		}

		public boolean isWrapperFor(Class<?> iface) throws SQLException {
			return this.conn.isWrapperFor(iface);
		}

		public Statement createStatement() throws SQLException {
			return this.conn.createStatement();
		}

		public PreparedStatement prepareStatement(String sql) throws SQLException {
			return this.conn.prepareStatement(sql);
		}

		public CallableStatement prepareCall(String sql) throws SQLException {
			return this.conn.prepareCall(sql);
		}

		public String nativeSQL(String sql) throws SQLException {
			return this.conn.nativeSQL(sql);
		}

		public void setAutoCommit(boolean autoCommit) throws SQLException {
			this.conn.setAutoCommit(autoCommit);
		}

		public boolean getAutoCommit() throws SQLException {
			return this.conn.getAutoCommit();
		}

		public void commit() throws SQLException {
			this.conn.commit();
		}

		public void rollback() throws SQLException {
			this.conn.rollback();
		}

		public boolean isClosed() throws SQLException {
			return this.conn.isClosed();
		}

		public DatabaseMetaData getMetaData() throws SQLException {
			return this.conn.getMetaData();
		}

		public void setReadOnly(boolean readOnly) throws SQLException {
			this.conn.setReadOnly(readOnly);
		}

		public boolean isReadOnly() throws SQLException {
			return this.conn.isReadOnly();
		}

		public void setCatalog(String catalog) throws SQLException {
			this.conn.setCatalog(catalog);
		}

		public String getCatalog() throws SQLException {
			return this.conn.getCatalog();
		}

		public void setTransactionIsolation(int level) throws SQLException {
			this.conn.setTransactionIsolation(level);
		}

		public int getTransactionIsolation() throws SQLException {
			return this.conn.getTransactionIsolation();
		}

		public SQLWarning getWarnings() throws SQLException {
			return this.conn.getWarnings();
		}

		public void clearWarnings() throws SQLException {
			this.conn.clearWarnings();
		}

		public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
			return this.conn.createStatement(resultSetType, resultSetConcurrency);
		}

		public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
				throws SQLException {
			return this.conn.prepareStatement(sql, resultSetType, resultSetConcurrency);
		}

		public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
				throws SQLException {
			return this.conn.prepareCall(sql, resultSetType, resultSetConcurrency);
		}

		public Map<String, Class<?>> getTypeMap() throws SQLException {
			return this.conn.getTypeMap();
		}

		public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
			this.conn.setTypeMap(map);
		}

		public void setHoldability(int holdability) throws SQLException {
			this.conn.setHoldability(holdability);
		}

		public int getHoldability() throws SQLException {
			return this.conn.getHoldability();
		}

		public Savepoint setSavepoint() throws SQLException {
			return this.conn.setSavepoint();
		}

		public Savepoint setSavepoint(String name) throws SQLException {
			return this.conn.setSavepoint(name);
		}

		public void rollback(Savepoint savepoint) throws SQLException {
			this.conn.rollback(savepoint);
		}

		public void releaseSavepoint(Savepoint savepoint) throws SQLException {
			this.conn.releaseSavepoint(savepoint);
		}

		public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
				throws SQLException {
			return this.conn.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
		}

		public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
				int resultSetHoldability) throws SQLException {
			return this.conn.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
		}

		public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
				int resultSetHoldability) throws SQLException {
			return this.conn.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
		}

		public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
			return this.conn.prepareStatement(sql, autoGeneratedKeys);
		}

		public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
			return this.conn.prepareStatement(sql, columnIndexes);
		}

		public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
			return this.conn.prepareStatement(sql, columnNames);
		}

		public Clob createClob() throws SQLException {
			return this.conn.createClob();
		}

		public Blob createBlob() throws SQLException {
			return this.conn.createBlob();
		}

		public NClob createNClob() throws SQLException {
			return this.conn.createNClob();
		}

		public SQLXML createSQLXML() throws SQLException {
			return this.conn.createSQLXML();
		}

		public boolean isValid(int timeout) throws SQLException {
			return this.conn.isValid(timeout);
		}

		public void setClientInfo(String name, String value) throws SQLClientInfoException {
			this.conn.setClientInfo(name,value);
		}

		public void setClientInfo(Properties properties) throws SQLClientInfoException {
			this.conn.setClientInfo(properties);
		}

		public String getClientInfo(String name) throws SQLException {
			return this.conn.getClientInfo(name);
		}

		public Properties getClientInfo() throws SQLException {
			return this.conn.getClientInfo();
		}

		public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
			return this.conn.createArrayOf(typeName, elements);
		}

		public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
			return this.conn.createStruct(typeName, attributes);
		}

		public void setSchema(String schema) throws SQLException {
			this.conn.setSchema(schema);
		}

		public String getSchema() throws SQLException {
			return this.conn.getSchema();
		}

		public void abort(Executor executor) throws SQLException {
			this.conn.abort(executor);
		}

		public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
			this.conn.setNetworkTimeout(executor, milliseconds);
		}

		public int getNetworkTimeout() throws SQLException {
			return this.conn.getNetworkTimeout();
		}

	}

	// 下面的方法，我们不理会哈

	public PrintWriter getLogWriter() throws SQLException {
		return null;
	}

	public void setLogWriter(PrintWriter out) throws SQLException {
	}

	public void setLoginTimeout(int seconds) throws SQLException {
	}

	public int getLoginTimeout() throws SQLException {
		return 0;
	}

	public <T> T unwrap(Class<T> iface) throws SQLException {
		return null;
	}

	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return false;
	}

	public Connection getConnection(String username, String password) throws SQLException {
		return null;
	}

	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		return null;
	}

}
